
AddCSLuaFile( "cl_init.lua" )
AddCSLuaFile( "shared.lua" )
include( 'shared.lua' )

---------------------------------------------------------
--   Name: Initialize
---------------------------------------------------------
function ENT:Initialize()

    self.Entity:SetModel( self.Model )

    self.Entity:PhysicsInit( SOLID_VPHYSICS )
    self.Entity:SetMoveType( MOVETYPE_VPHYSICS ) 
    self.Entity:SetSolid( SOLID_VPHYSICS ) 

    --Don't collide with the player
    self.Entity:SetCollisionGroup( COLLISION_GROUP_WEAPON )

    -- Wake the physics object up. It's time to have fun!
    local phys = self.Entity:GetPhysicsObject()
    if (phys:IsValid()) then
        phys:EnableMotion(false) --Freeze it
        phys:Wake()
    end

    self.ToolGun = nil
    self.FireCount = 0
    if self.WirePort == nil then
        self.WirePort = ""
    end

    --Wire Addon
    if WireAddon == 1 then
        self.Inputs = WireLib.CreateSpecialInputs(self.Entity, {"LeftClick", "RightClick", "Reload", "Freeze", "Mode", "Isolate", "Config", "WirePort"}, {"NORMAL", "NORMAL", "NORMAL", "NORMAL", "STRING", "NORMAL", "TABLE", "STRING"})
        self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"FireCount", "Mode", "Isolated", "Config"}, {"NORMAL", "STRING", "NORMAL", "TABLE"})
        self.CanLeft = true
        self.CanRight = true
        self.CanReload = true
        Wire_TriggerOutput(self.Entity, "FireCount", self.FireCount)
    end
    self:Update()
end

--Wire input, only triggers on rising edge, must set value to < 1 before triggering again
function ENT:TriggerInput(iname, value)
    if (iname == "LeftClick") then
        if (value >= 1) && self.CanLeft then
            self.CanLeft = false
            AddToolEvent(self.Delay, self, TOOL_ACTION_LEFT)
        else
            self.CanLeft = true
        end

    elseif (iname == "RightClick") then
        if (value >= 1) && self.CanRight then
            self.CanRight = false
            AddToolEvent(self.Delay, self, TOOL_ACTION_RIGHT)
        else
            self.CanRight = true
        end

    elseif (iname == "Reload") then
        if (value >= 1) && self.CanReload then
            self.CanReload = false
            AddToolEvent(self.Delay, self, TOOL_ACTION_RELOAD)
        else
            self.CanReload = true
        end

    elseif (iname == "Freeze") then
        local phys = self.Entity:GetPhysicsObject()
        if(value ~= 0) then
            phys:EnableMotion(false)
            phys:Wake()
        else
            phys:EnableMotion(true)
            phys:Wake()
        end

    elseif (iname == "Mode") then
        if (value ~= "" && value ~= self.Mode) then
            if !self.ToolGun.Tool[value] then
                self.Owner:PrintMessage(HUD_PRINTCENTER,"Tool Invalid")
            elseif !AllowTool(value) then
                self.Owner:PrintMessage(HUD_PRINTCENTER,"That tool is disabled for Turret use")
            else
                self.Mode = value
                local Vars = {}
                for k,v in pairs(self.ToolGun.Tool[value].ClientConVar) do
                    local key = value .. "_" .. k
                    local val = self.Owner:GetInfo(key)
                    Vars[k] = val
                end
                self.Vars = Vars 
                self:Update()
            end
        end

    elseif (iname == "Isolate") then
        if(value ~= 0) then
            self.Isolated = 1
        else
            self.Isolated = 0
        end
        self:Update()

    elseif (iname == "Config") then
        -- self.Owner:ChatPrint(value.ToString())
        -- PrintTable(self.Vars)
        if(type(value) == "table") then
            for Key,Type in pairs(value["stypes"]) do
                if(Type == "n") then
                    if(type(self.Vars[Key]) == "number" || self.Vars[Key] == nil) then
                        self.Vars[Key] = value["s"][Key]
                    end
                elseif(Type == "s") then
                    if(type(self.Vars[Key]) == "string" || self.Vars[Key] == nil) then
                        self.Vars[Key] = value["s"][Key]
                    end
                elseif(Type == "r") then
                elseif(Type == "t") then
                elseif(Type == "e") then
                elseif(Type == "a") then
                elseif(Type == "v") then
                elseif(Type == "m") then
                elseif(Type == "b") then
                elseif(Type == "c") then
                elseif(Type == "q") then
                elseif(Type == "xv2") then
                elseif(Type == "xv4") then
                elseif(Type == "xm2") then
                elseif(Type == "xm4") then
                elseif(Type == "xwl") then
                elseif(Type == "xrd") then
                end
            end
            self:Update()
        end

    elseif (iname == "WirePort") then
        self.WirePort = value
    end
end


function ENT:Timer(Fun, Args)
    self.Tool.TurretEntity = self.Entity
    self.Tool.GetClientInfo =     ToolOveride.GetClientInfo        --This will call our function rather than the ones on the ToolObj metatable
    self.Tool.GetClientNumber = ToolOveride.GetClientNumber
    timer.Create = function(ID, Delay, Reps, Fun, Args) ToolOveride.RealTimerCreate(ID, Delay, Reps, function(Fun, Args) self:Timer(Fun, Args) end, Fun, Args) end
    local oldGetSWEP = self.Tool.GetSWEP
    self.Tool.GetSWEP = ToolOveride.GetSWEP
    Fun(Args)
    self.Tool.GetSWEP = oldGetSWEP
    self.Tool.GetClientInfo = nil
    self.Tool.GetClientNumber = nil
    timer.Create = ToolOveride.RealTimerCreate
end

function ENT:DoToolAction(action)
    local trace
    if self.Model == "models/jaanus/wiretool/wiretool_range.mdl" || self.Model == "models/jaanus/wiretool/wiretool_grabber_forcer.mdl" || self.Model == "models/jaanus/wiretool/wiretool_siren.mdl" then
        trace = util.QuickTrace( self.Entity:GetPos() + self.Entity:GetUp() * 5 , self.Entity:GetUp() * self.Range,self.Entity)
    elseif self.Model == "models/weapons/w_pistol.mdl" then
        trace = util.QuickTrace( self.Entity:GetPos() - self.Entity:GetForward() * 5 , -self.Entity:GetForward() * self.Range,self.Entity)
    else
        trace = util.QuickTrace( self.Entity:GetPos() + self.Entity:GetForward() * 5 , self.Entity:GetForward() * self.Range,self.Entity)
    end

    if trace.Hit && gamemode.Call( "CanTool", self.Owner, trace, self.Mode ) then
        --Ask the gamemode if it's ok to do this

        self.Tool.TurretEntity = self.Entity

        self.Tool.GetClientInfo =     ToolOveride.GetClientInfo        --This will call our function rather than the ones on the ToolObj metatable
        self.Tool.GetClientNumber = ToolOveride.GetClientNumber
        -- self.Tool.SetStage =         ToolOveride.SetStage        --khm: no idea why this was overwritten originally but i'll leave the code just in case taking them out was a bad idea later on
        -- self.Tool.GetStage =         ToolOveride.GetStage
        timer.Create = function(ID, Delay, Reps, Fun, Args) ToolOveride.RealTimerCreate(ID, Delay, Reps, function(Fun, Args) self:Timer(Fun, Args) end, Fun, Args) end

        local oldGetSWEP = self.Tool.GetSWEP
        self.Tool.GetSWEP = ToolOveride.GetSWEP

        local r = false
        if(WireAddon == 1 && (self.Mode == "wire" || self.Mode == "wire_adv")) then
            if action == TOOL_ACTION_LEFT then
                if(trace.Entity:IsValid() && self.WirePort) then
                    local inputs = trace.Entity.Inputs
                    local WirePort = self.WirePort:gsub(" ", "")
                    if !self.ToolGun.WireDestPort || !(self.ToolGun.WireDestEnt && self.ToolGun.WireDestEnt:IsValid()) then
                        if inputs then
                            for name, v in pairs(inputs) do
                                if name:gsub(" ", "") == WirePort then
                                    self.ToolGun.WireDestPort = v
                                    break
                                end
                            end
                            local C
                            if self.Mode == "wire" then
                                C = Color(self.Vars["color_r"], self.Vars["color_g"], self.Vars["color_b"])
                            else
                                C = Color(self.Vars["r"], self.Vars["g"], self.Vars["b"])
                            end
                            if self.ToolGun.WireDestPort && Wire_Link_Start(self.ToolGun:GetCreationID(), trace.Entity, trace.Entity:WorldToLocal(trace.HitPos), self.ToolGun.WireDestPort["Name"], self.Vars["material"], C, self.Vars["width"]) then
                                self.ToolGun.WireDestEnt = trace.Entity
                                r = true
                            else
                                self.ToolGun.WireDestPort = nil
                                self.ToolGun.WireDestEnt = nil
                            end
                        end
                    else
                        local outputs = trace.Entity.Outputs
                        local Port = nil
                        if outputs then
                            for name, v in pairs(outputs) do
                                if name:gsub(" ", "") == WirePort && v["Type"] == self.ToolGun.WireDestPort["Type"] then
                                    Port = v
                                    break
                                end
                            end
                        end
                        if !Port then
                            if self.ToolGun.WireDestPort["Type"] == "WIRELINK" && WirePort == "link" && (inputs or outputs or ent.Base == "base_wire_entity") then
                                Port = {["Name"] = "link"}
                            elseif self.ToolGun.WireDestPort["Type"] == "ENTITY" then
                                Wire_Link_Cancel(self.ToolGun:GetCreationID())
                                WireLib.TriggerInput(self.ToolGun.WireDestEnt, self.ToolGun.WireDestPort["Name"], trace.Entity)
                                WireLib.AddNotify(self.Owner, "Triggered entity input '" .. self.ToolGun.WireDestPort["Name"] .. "' with '" .. tostring(trace.Entity) .. "'.", NOTIFY_GENERIC, 7)
                                self.ToolGun.WireDestPort = nil
                                self.ToolGun.WireDestEnt = nil
                                r = true
                            elseif !outputs then
                                WireLib.AddNotify(self.Owner, "The selected entity has no outputs. Please select a different entity.", NOTIFY_GENERIC, 7)
                            end
                        end
                        if Port then
                            Wire_Link_End(self.ToolGun:GetCreationID(), trace.Entity, trace.Entity:WorldToLocal(trace.HitPos), Port["Name"], self.Owner)
                            self.ToolGun.WireDestPort = nil
                            self.ToolGun.WireDestEnt = nil
                            r = true
                        end
                    end
                end
            elseif action == TOOL_ACTION_RIGHT then
                if self.ToolGun.WireDestPort && self.ToolGun.WireDestEnt && self.ToolGun.WireDestEnt:IsValid() then
                    Wire_Link_Node(self.ToolGun:GetCreationID(), trace.Entity, trace.Entity:WorldToLocal(trace.HitPos+trace.HitNormal))
                    r = true
                end
            elseif action == TOOL_ACTION_RELOAD then
                if self.ToolGun.WireDestPort || (self.ToolGun.WireDestEnt && self.ToolGun.WireDestEnt:IsValid()) then
                    Wire_Link_Cancel(self.ToolGun:GetCreationID())
                    self.ToolGun.WireDestPort = nil
                    self.ToolGun.WireDestEnt = nil
                    r = true
                elseif(trace.Entity:IsValid() && self.WirePort) then
                    local inputs = trace.Entity.Inputs
                    local WirePort = self.WirePort:gsub(" ", "")
                    if inputs then
                        for name, v in pairs(inputs) do
                            if name:gsub(" ", "") == WirePort then
                                Wire_Link_Clear(trace.Entity, name)
                                r = true
                                break
                            end
                        end
                    end
                end
            end
        else
            --self.Tool:Think() -- TODO: Find some way to implement this as that some stools need it
            if action == TOOL_ACTION_LEFT then
                r = self.Tool:LeftClick(trace)
            elseif action == TOOL_ACTION_RIGHT then
                r = self.Tool:RightClick(trace)
            elseif action == TOOL_ACTION_RELOAD then
                r = self.Tool:Reload(trace)
            end
        end

        self.CanReload = true
        self.CanLeft = true
        self.CanRight = true

        if r ~= false then
            --Do the effect
            self.Entity:EmitSound(self.ShootSound,100,50)

            local effectdata = EffectData()
                effectdata:SetOrigin( trace.HitPos )
                effectdata:SetNormal( trace.HitNormal )
                effectdata:SetEntity( trace.Entity )
                effectdata:SetAttachment( trace.PhysicsBone  )
                util.Effect( "selection_indicator", effectdata )

            local effectdata = EffectData()
                effectdata:SetStart( self.Entity:GetPos() )
                effectdata:SetOrigin( trace.HitPos )
                effectdata:SetScale( 1 )
                effectdata:SetEntity( self.Entity )
                util.Effect( "ToolTracer", effectdata )
            if WireAddon == 1 then
                self.FireCount = self.FireCount + 1
                Wire_TriggerOutput(self.Entity, "FireCount", self.FireCount)
            end
        end

        self.Tool.GetSWEP = oldGetSWEP


        self.Tool.GetClientInfo = nil
        self.Tool.GetClientNumber = nil
        -- self.Tool.SetStage = nil
        -- self.Tool.GetStage = nil
        timer.Create = ToolOveride.RealTimerCreate

    end
end

function ENT:SetConVar(name,value)
    self.Vars[name] = value
end

function ENT:Update()

    self:CheckToolGun()

    local tname = string.gsub((self.Tool.Name or self.Mode),'#','')
    local lbl = "Stool Turret " .. tname.. "\n"

    lbl = lbl .. "Delay: " .. self.Delay .. "\n"
    lbl = lbl .. "Tool config:\n"
    local E2Table = {}
    if WireAddon == 1 then
        E2Table["size"] = 0
        E2Table["s"] = {}
        E2Table["stypes"] = {}
        E2Table["istable"] = true
        E2Table["depth"] = 0
        E2Table["n"] = {}
        E2Table["ntypes"] = {}
    end
    for k,v in pairs(self.Vars) do
        lbl = lbl .. k .. ": " .. v .."\n"
        if WireAddon == 1 then
        if(type(v) == "string") then
        E2Table["size"] = E2Table["size"] + 1
        E2Table["s"][k] = v
        E2Table["stypes"][k] = "s"
        elseif(type(v) == "number") then
        E2Table["size"] = E2Table["size"] + 1
        E2Table["s"][k] = v
        E2Table["stypes"][k] = "n"
        elseif(type(v) == "boolean") then
        E2Table["size"] = E2Table["size"] + 1
        E2Table["s"][k] = v == true
        E2Table["stypes"][k] = "n"
        elseif(type(v) == "table") then
        elseif(type(v) == "Vector") then
        E2Table["size"] = E2Table["size"] + 1
        E2Table["s"][k] = v
        E2Table["stypes"][k] = "v"
        elseif(type(v) == "Angle") then
        E2Table["size"] = E2Table["size"] + 1
        E2Table["s"][k] = {v.p, v.y, v.r}
        E2Table["stypes"][k] = "a"
        elseif(type(v) == "Color") then
        end
        end
    end
    self.Entity:SetNWString("label",lbl)
    if WireAddon == 1 then
        Wire_TriggerOutput(self.Entity, "Mode", self.Mode)
        Wire_TriggerOutput(self.Entity, "Config", E2Table)
    end
end

---------------------------------------------------------
--- gmod_tool Emulation functions
--- It is not nessesary to overide every function
---------------------------------------------------------

ToolOveride = {}

ToolOveride.RealTimerCreate = timer.Create
function ToolOveride.GetClientInfo(tool, property)
    local r = tool.TurretEntity.Vars[property]
    if r ~= nil then
        return r
    end
    return tool.TurretEntity.Owner:GetInfo(tool.TurretEntity.Mode .. "_" .. property)
end
function ToolOveride.GetClientNumber(tool, property, default )
    local r = tonumber(tool.TurretEntity.Vars[property]) or default
    if r ~= nil then
        return r
    end
    return tool.TurretEntity.Owner:GetInfoNum(tool.TurretEntity.Mode .. "_" .. property)
end
function ToolOveride.SetStage(tool, i )

    if ( SERVER ) then
        tool.TurretEntity.Owner:SetNWInt( tool.TurretEntity.Mode .. "_stage", i, true )
    end

end
function ToolOveride.GetStage(tool)
    return tool.TurretEntity.Owner:GetNWInt( tool.TurretEntity.Mode .. "_stage", 0 )
end
function ToolOveride.GetSWEP(tool)
    return tool.TurretEntity
end



function ENT:CheckToolGun()
    if(self.ToolGun && self.ToolGun:IsValid()) then
        local toolguns = self.Owner:GetWeapon("gmod_tool")
        if(self.Isolated ~= 0 && self.ToolGun == toolguns) then
            self.ToolGun = nil
        elseif(self.Isolated == 0 && self.ToolGun ~= toolguns) then
            self:RemoveCallOnRemove("Tool Gun Cleanup")
            self.ToolGun:Remove()
            self.ToolGun = nil
        end
    end
    if !(self.ToolGun && self.ToolGun:IsValid()) then
        --Disaster the player has died and the tool gun was deleted!!
        if(self.Isolated ~= 0) then
            self.ToolGun = ents.Create("gmod_tool")
            self.ToolGun:Spawn()
            -- self.ToolGun:SetOwner(self)
            self:CallOnRemove("Tool Gun Cleanup", self.ToolGun.Remove)
        elseif !(self.Owner && self.Owner:IsValid()) then
            --Worse, the player has left the game

            --Create our own toolgun
            self.ToolGun = ents.Create("gmod_tool")
            self.ToolGun:Spawn()
        else
            -- local toolguns = ents.FindByClass("gmod_tool")
            -- for _,gun in ipairs(toolguns) do
                -- if gun:GetOwner() == self.Owner then 
                    -- self.ToolGun = gun
                -- end
            -- end
            self.ToolGun = self.Owner:GetWeapon("gmod_tool")
        end
    end
    if WireAddon == 1 then
        Wire_TriggerOutput(self.Entity, "Isolated", self.Isolated)
    end

    self.Tool = self.ToolGun.Tool[self.Mode]
end

--SWEP Functions
function ENT:GetOwner()
    return self.Owner
end

function ENT:CheckLimit( str ) 
    local ply = self:GetOwner()
    if ply.CheckLimit then return ply:CheckLimit( str ) end
    return true
end

--Numpad functions

local function Left( pl, ent )
    if(ent == nil || !ent:IsValid()) then return false end
    AddToolEvent(ent.Delay,ent, 1)
end

local function Right( pl, ent )
    if(ent == nil || !ent:IsValid()) then return false end
    AddToolEvent(ent.Delay,ent, 2)
end

local function Reload( pl, ent)
    if(ent == nil || !ent:IsValid()) then return false end
    AddToolEvent(ent.Delay,ent, 3)
end

--register numpad functions
numpad.Register( "StoolTurret_Left", Left )
numpad.Register( "StoolTurret_Right", Right )
numpad.Register( "StoolTurret_Reload", Reload )

function ENT:OnRestore()
    Wire_Restored(self.Entity)
    self.ToolGun = nil
    self.FireCount = 0
    if self.WirePort == nil then
        self.WirePort = ""
    end

    --Wire Addon
    if WireAddon == 1 then
        self.Inputs = WireLib.CreateSpecialInputs(self.Entity, {"LeftClick", "RightClick", "Reload", "Freeze", "Mode", "Isolate", "Config", "WirePort"}, {"NORMAL", "NORMAL", "NORMAL", "NORMAL", "STRING", "NORMAL", "TABLE", "STRING"})
        self.Outputs = WireLib.CreateSpecialOutputs(self.Entity, {"FireCount", "Mode", "Isolated", "Config"}, {"NORMAL", "STRING", "NORMAL", "TABLE"})
        self.CanLeft = true
        self.CanRight = true
        self.CanReload = true
        Wire_TriggerOutput(self.Entity, "FireCount", self.FireCount)
    end
    self:Update()
end

function ENT:OnRemove()
    Wire_Remove(self.Entity)
end


function ENT:BuildDupeInfo()
    //return WireLib.BuildDupeInfo( self.Entity )
    if (not self.Inputs) then return end

    local info = { Wires = {} }
    for k,input in pairs(self.Inputs) do
        if (input.Src) and (input.Src:IsValid()) then
            info.Wires[k] = {
                StartPos = input.StartPos,
                Material = input.Material,
                Color = input.Color,
                Width = input.Width,
                Src = input.Src:EntIndex(),
                SrcId = input.SrcId,
                SrcPos = Vector(0, 0, 0),
            }

            if (input.Path) then
                info.Wires[k].Path = {}

                for _,v in ipairs(input.Path) do
                    if (v.Entity) and (v.Entity:IsValid()) then
                        table.insert(info.Wires[k].Path, { Entity = v.Entity:EntIndex(), Pos = v.Pos })
                    end
                end

                local n = table.getn(info.Wires[k].Path)
                if (n > 0) and (info.Wires[k].Path[n].Entity == info.Wires[k].Src) then
                    info.Wires[k].SrcPos = info.Wires[k].Path[n].Pos
                    table.remove(info.Wires[k].Path, n)
                end
            end
        end
    end

    return info
end

--Copied form Wire
function ENT:ApplyDupeInfo(ply, ent, info, GetEntByID)
    //WireLib.ApplyDupeInfo( ply, ent, info, GetEntByID )
    if (info.Wires) then
        for k,input in pairs(info.Wires) do

            Wire_Link_Start(ply:UniqueID(), ent, input.StartPos, k, input.Material, input.Color, input.Width)

            if (input.Path) then
                for _,v in ipairs(input.Path) do

                    local ent2 = GetEntByID(v.Entity)
                    if (!ent2) or (!ent2:IsValid()) then ent2 = ents.GetByIndex(v.Entity) end
                    if (ent2) or (ent2:IsValid()) then
                        Wire_Link_Node(ply:UniqueID(), ent2, v.Pos)
                    else
                        Msg("ApplyDupeInfo: Error, Could not find the entity for wire path\n")
                    end
                end
            end

            local ent2 = GetEntByID(input.Src)
            if (!ent2) or (!ent2:IsValid()) then ent2 = ents.GetByIndex(input.Src) end
            if (ent2) or (ent2:IsValid()) then
                Wire_Link_End(ply:UniqueID(), ent2, input.SrcPos, input.SrcId)
            else
                Msg("ApplyDupeInfo: Error, Could not find the output entity\n")
            end
        end
    end
end

//
//new duplicator stuff
//
function ENT:PreEntityCopy()
    //build the DupeInfo table and save it as an entity mod
    local DupeInfo = self:BuildDupeInfo()
    if DupeInfo then
        duplicator.StoreEntityModifier( self.Entity, "WireDupeInfo", DupeInfo )
    end
end

function ENT:PostEntityPaste( Player, Ent, CreatedEntities )
    //apply the DupeInfo
    if (Ent.EntityMods) and (Ent.EntityMods.WireDupeInfo) then
        Ent:ApplyDupeInfo(Player, Ent, Ent.EntityMods.WireDupeInfo, function(id) return CreatedEntities[id] end)
    end
end